/*
 * This file is part of aion-unique <aion-unique.org>.
 *
 *  aion-unique is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-unique is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-unique.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.commons.versionning;

import java.io.File;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Locale;

/**
 * The Locator is a utility class which is used to find certain items in the environment.
 * 
 * @since Ant 1.6
 */
public final class Locator
{
	/**
	 * Not instantiable
	 */
	private Locator()
	{
	}

	/**
	 * Find the directory or jar file the class has been loaded from.
	 * 
	 * @param c
	 *            the class whose location is required.
	 * @return the file or jar with the class or null if we cannot determine the location.
	 * 
	 * @since Ant 1.6
	 */
	public static File getClassSource(Class<?> c)
	{
		String classResource = c.getName().replace('.', '/') + ".class";
		return getResourceSource(c.getClassLoader(), classResource);
	}

	/**
	 * Find the directory or jar a given resource has been loaded from.
	 * 
	 * @param c
	 *            the classloader to be consulted for the source.
	 * @param resource
	 *            the resource whose location is required.
	 * 
	 * @return the file with the resource source or null if we cannot determine the location.
	 * 
	 * @since Ant 1.6
	 */
	public static File getResourceSource(ClassLoader c, String resource)
	{
		if (c == null)
		{
			c = Locator.class.getClassLoader();
		}
		URL url = null;
		if (c == null)
		{
			url = ClassLoader.getSystemResource(resource);
		}
		else
		{
			url = c.getResource(resource);
		}
		if (url != null)
		{
			String u = url.toString();
			if (u.startsWith("jar:file:"))
			{
				int pling = u.indexOf("!");
				String jarName = u.substring(4, pling);
				return new File(fromURI(jarName));
			}
			else if (u.startsWith("file:"))
			{
				int tail = u.indexOf(resource);
				String dirName = u.substring(0, tail);
				return new File(fromURI(dirName));
			}
		}
		return null;
	}

	/**
	 * Constructs a file path from a <code>file:</code> URI.
	 * 
	 * <p>
	 * Will be an absolute path if the given URI is absolute.
	 * </p>
	 * 
	 * <p>
	 * Swallows '%' that are not followed by two characters, doesn't deal with non-ASCII characters.
	 * </p>
	 * 
	 * @param uri
	 *            the URI designating a file in the local filesystem.
	 * @return the local file system path for the file.
	 * @since Ant 1.6
	 */
	public static String fromURI(String uri)
	{
		URL url = null;
		try
		{
			url = new URL(uri);
		}
		catch (MalformedURLException emYouEarlEx)
		{
			// Ignore malformed exception
		}
		if (url == null || !("file".equals(url.getProtocol())))
		{
			throw new IllegalArgumentException("Can only handle valid file: URIs");
		}
		StringBuffer buf = new StringBuffer(url.getHost());
		if (buf.length() > 0)
		{
			buf.insert(0, File.separatorChar).insert(0, File.separatorChar);
		}
		String file = url.getFile();
		int queryPos = file.indexOf('?');
		buf.append((queryPos < 0) ? file : file.substring(0, queryPos));

		uri = buf.toString().replace('/', File.separatorChar);

		if (File.pathSeparatorChar == ';' && uri.startsWith("\\") && uri.length() > 2 && Character.isLetter(uri.charAt(1)) && uri.lastIndexOf(':') > -1)
		{
			uri = uri.substring(1);
		}
		String path = decodeUri(uri);
		return path;
	}

	/**
	 * Decodes an Uri with % characters.
	 * 
	 * @param uri
	 *            String with the uri possibly containing % characters.
	 * @return The decoded Uri
	 */
	private static String decodeUri(String uri)
	{
		if (uri.indexOf('%') == -1)
		{
			return uri;
		}
		StringBuffer sb = new StringBuffer();
		CharacterIterator iter = new StringCharacterIterator(uri);
		for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
		{
			if (c == '%')
			{
				char c1 = iter.next();
				if (c1 != CharacterIterator.DONE)
				{
					int i1 = Character.digit(c1, 16);
					char c2 = iter.next();
					if (c2 != CharacterIterator.DONE)
					{
						int i2 = Character.digit(c2, 16);
						sb.append((char) ((i1 << 4) + i2));
					}
				}
			}
			else
			{
				sb.append(c);
			}
		}
		String path = sb.toString();
		return path;
	}

	/**
	 * Get the File necessary to load the Sun compiler tools. If the classes are available to this class, then no
	 * additional URL is required and null is returned. This may be because the classes are explicitly in the class path
	 * or provided by the JVM directly.
	 * 
	 * @return the tools jar as a File if required, null otherwise.
	 */
	public static File getToolsJar()
	{
		// firstly check if the tools jar is already in the classpath
		boolean toolsJarAvailable = false;
		try
		{
			// just check whether this throws an exception
			Class.forName("com.sun.tools.javac.Main");
			toolsJarAvailable = true;
		}
		catch (Exception e)
		{
			try
			{
				Class.forName("sun.tools.javac.Main");
				toolsJarAvailable = true;
			}
			catch (Exception e2)
			{
				// ignore
			}
		}
		if (toolsJarAvailable)
		{
			return null;
		}
		// couldn't find compiler - try to find tools.jar
		// based on java.home setting
		String javaHome = System.getProperty("java.home");
		if (javaHome.toLowerCase(Locale.US).endsWith("jre"))
		{
			javaHome = javaHome.substring(0, javaHome.length() - 4);
		}
		File toolsJar = new File(javaHome + "/lib/tools.jar");
		if (!toolsJar.exists())
		{
			System.out.println("Unable to locate tools.jar. " + "Expected to find it in " + toolsJar.getPath());
			return null;
		}
		return toolsJar;
	}

	/**
	 * Get an array of URLs representing all of the jar files in the given location. If the location is a file, it is
	 * returned as the only element of the array. If the location is a directory, it is scanned for jar files.
	 * 
	 * @param location
	 *            the location to scan for Jars.
	 * 
	 * @return an array of URLs for all jars in the given location.
	 * 
	 * @exception MalformedURLException
	 *                if the URLs for the jars cannot be formed.
	 */
	public static URL[] getLocationURLs(File location) throws MalformedURLException
	{
		return getLocationURLs(location, new String[]
		{ ".jar" });
	}

	/**
	 * Get an array of URLs representing all of the files of a given set of extensions in the given location. If the
	 * location is a file, it is returned as the only element of the array. If the location is a directory, it is
	 * scanned for matching files.
	 * 
	 * @param location
	 *            the location to scan for files.
	 * @param extensions
	 *            an array of extension that are to match in the directory search.
	 * 
	 * @return an array of URLs of matching files.
	 * @exception MalformedURLException
	 *                if the URLs for the files cannot be formed.
	 */
	public static URL[] getLocationURLs(File location, final String[] extensions) throws MalformedURLException
	{

		URL[] urls = new URL[0];

		if (!location.exists())
		{
			return urls;
		}
		if (!location.isDirectory())
		{
			urls = new URL[1];
			String path = location.getPath();
			for (int i = 0; i < extensions.length; ++i)
			{
				if (path.toLowerCase().endsWith(extensions[i]))
				{
					urls[0] = location.toURI().toURL();
					break;
				}
			}
			return urls;
		}
		File[] matches = location.listFiles(new FilenameFilter()
		{
			@Override
			public boolean accept(File dir, String name)
			{
				for (int i = 0; i < extensions.length; ++i)
				{
					if (name.toLowerCase().endsWith(extensions[i]))
					{
						return true;
					}
				}
				return false;
			}
		});
		urls = new URL[matches.length];
		for (int i = 0; i < matches.length; ++i)
		{
			urls[i] = matches[i].toURI().toURL();
		}
		return urls;
	}
}
